//***************************************************************************
//   Copyright(c)2020, Xidian University D405.
//           All rights reserved
//
//   File name       :   tx_fifo_top.v
//   Module name     :   tx_fifo_top
//   Author          :   Zhao Yuchen
//   Date            :   2022/06/24
//   Version         :   v0.95
//   Edited by       :   Zhao Yuchen
//***************************************************************************
module tx_fifo_top_10g #
    (
        parameter MAC_DWIDTH = 64  , // MAC Data Width
        parameter MAC_KWIDTH = 8   , // MAC Keep Width
        parameter MAC_KBITS  = 3   , // MAC Keep Bits
        parameter MAC_UWIDTH = 4   , // MAC User Width
        parameter DMA_DWIDTH = 128 , // DMA Data Width
        parameter MEM_AWIDTH = 10  , // MEM Addr Width
        parameter LEN_DWIDTH = 11  , // LEN Data Width
        parameter LEN_AWIDTH = 10    // LEN Addr Width
    )
    (
        // Clocks and resets
        input                                 clkr              ,
        input                                 clkw              ,
        input                                 rstn_rclk       ,
        input                                 rstn_wclk       ,
        // AXI_Stream Signal
        input                                 axi_ttready_i     ,
        output reg                            axi_ttvalid_o     ,
        output reg                            axi_ttlast_o      ,
        output reg  [MAC_KWIDTH-1 : 0]        axi_ttkeep_o      ,
        output reg  [MAC_DWIDTH-1 : 0]        axi_ttdata_o      ,
        output wire [MAC_UWIDTH-1 : 0]        axi_ttuser_o      ,

        // DMA Control
        input                                 start_send_e_i    ,
        output  reg                           send_done_o       ,
        input                                 send_done_clr_i   ,
        input   [LEN_DWIDTH-1 : 0]            frame_len_i       ,

        // Tx_FIFO
        input                                 wr_tx_fifo_e_i    ,
        input  [DMA_DWIDTH-1 : 0]             wr_tx_fifo_data_i ,
        output                                tx_fifo_full_o    ,
        input  wire [11:0]                    ram_dp_cfg_register
    );


    localparam integer MEM_MAX_DEPTH = 'd64;

    wire [7 : 0]            tkeep_last;
    wire [3 : 0]            last_data_valid_byte;
    reg   [7 : 0]           write_index;
    wire  [7 : 0]           write_len;
    reg                     write_active;
    wire                    wnext;
    wire                    write_done;
    reg [LEN_DWIDTH-1 : 0]  frame_len_r;
    reg                     last_data_accepted;

    assign axi_ttuser_o = {MAC_UWIDTH{1'b0}};
    assign write_len = (frame_len_r + 11'd7) / 11'd8 - 11'd1;
    assign tkeep_last = (last_data_valid_byte == 'd0) ? ({8{1'b1}}) : ({8{1'b1}} >> (8-last_data_valid_byte));
    assign last_data_valid_byte = ((frame_len_r) % 'd8 == 'd0) ? 'd8 : ((frame_len_r) % 'd8);
    assign wnext = axi_ttready_i & axi_ttvalid_o;
    assign write_done = wnext & axi_ttlast_o;

    reg send_done_clr_ff1;
    reg send_done_clr_ff2;
    
    always @(posedge clkr or negedge rstn_rclk)
    begin
        if(~rstn_rclk)
        begin
            send_done_clr_ff1 <= 1'b0;
            send_done_clr_ff2 <= 1'b0;
        end
        else
        begin
            send_done_clr_ff1 <= send_done_clr_i;
            send_done_clr_ff2 <= send_done_clr_ff1;
        end
    end
        
    always @(posedge clkr or negedge rstn_rclk)
    begin
        if(~rstn_rclk)
            send_done_o <= 1'b0;
        else if(write_done)
            send_done_o <= 1'b1;
        else if(send_done_clr_ff2)
            send_done_o <= 1'b0;
        else
            send_done_o <= send_done_o;
    end

    always @(posedge clkr or negedge rstn_rclk)
    begin
        if(~rstn_rclk)
            write_active <= 1'b0;
        else if(start_send_e_i)
            write_active <= 1'b1;
        else if(write_done)
            write_active <= 1'b0;
        else
            write_active <= write_active;
    end


    always @(posedge clkr or negedge rstn_rclk)
    begin
        if(~rstn_rclk)
            frame_len_r <= 'd0;
        else if(start_send_e_i)
            frame_len_r <= frame_len_i;
        else
            frame_len_r <= frame_len_r;
    end

    reg [LEN_DWIDTH-1 : 0] read_fifo_cnt;
    reg [LEN_DWIDTH-1 : 0] read_fifo_complete_cnt;
    reg [LEN_DWIDTH-1 : 0] axis_write_data_cnt;

    reg                    fifo_rd_en;
    reg                    fifo_rd_en_ff1;
    reg                    fifo_rd_en_ff2;
    wire                   fifo_data_vld;

    /* CACHE Total 32Bytes */

    reg [63 : 0]  CACHE_0;
    reg [63 : 0]  CACHE_1;
    reg [63 : 0]  CACHE_2;
    reg [63 : 0]  CACHE_3;
    reg [63 : 0]  CACHE_4;
    reg [63 : 0]  CACHE_5;
    reg [63 : 0]  CACHE_6;
    reg [63 : 0]  CACHE_7;
    reg [63 : 0]  next_rd_cache_reg;

    reg[4 : 0]   cache_wr_state;
    reg[4 : 0]   cache_wr_next_state;
    reg[8 : 0]   cache_rd_state;
    reg[8 : 0]   cache_rd_next_state;

    localparam W_CACHE_IDLE = 5'b00001;
    localparam W_CACHE_01   = 5'b00010;
    localparam W_CACHE_23   = 5'b00100;
    localparam W_CACHE_45   = 5'b01000;
    localparam W_CACHE_67   = 5'b10000;
    reg[4 : 0] W_CACHE_NEXT;

    localparam R_CACHE_IDLE = 9'b000000001;
    localparam R_CACHE_0    = 9'b000000010;
    localparam R_CACHE_1    = 9'b000000100;
    localparam R_CACHE_2    = 9'b000001000;
    localparam R_CACHE_3    = 9'b000010000;
    localparam R_CACHE_4    = 9'b000100000;
    localparam R_CACHE_5    = 9'b001000000;
    localparam R_CACHE_6    = 9'b010000000;
    localparam R_CACHE_7    = 9'b100000000;
    reg[8 : 0] R_CACHE_NEXT;

    always @(*)
    begin
        case(cache_wr_state)
            W_CACHE_IDLE: W_CACHE_NEXT = W_CACHE_01;
            W_CACHE_01  : W_CACHE_NEXT = W_CACHE_23;
            W_CACHE_23  : W_CACHE_NEXT = W_CACHE_45;
            W_CACHE_45  : W_CACHE_NEXT = W_CACHE_67;
            W_CACHE_67  : W_CACHE_NEXT = W_CACHE_01;
            default     : W_CACHE_NEXT = W_CACHE_01;
        endcase
    end

    always @(*)
    begin
        case(cache_rd_state)
            R_CACHE_IDLE: R_CACHE_NEXT = R_CACHE_0;
            R_CACHE_0   : R_CACHE_NEXT = R_CACHE_1;
            R_CACHE_1   : R_CACHE_NEXT = R_CACHE_2;
            R_CACHE_2   : R_CACHE_NEXT = R_CACHE_3;
            R_CACHE_3   : R_CACHE_NEXT = R_CACHE_4;
            R_CACHE_4   : R_CACHE_NEXT = R_CACHE_5;
            R_CACHE_5   : R_CACHE_NEXT = R_CACHE_6;
            R_CACHE_6   : R_CACHE_NEXT = R_CACHE_7;
            R_CACHE_7   : R_CACHE_NEXT = R_CACHE_0;
            default     : R_CACHE_NEXT = R_CACHE_0;
        endcase
    end

    always @(*)
    begin
       case(cache_rd_state)
        R_CACHE_IDLE: next_rd_cache_reg = CACHE_0;
        R_CACHE_0   : next_rd_cache_reg = CACHE_1;
        R_CACHE_1   : next_rd_cache_reg = CACHE_2;
        R_CACHE_2   : next_rd_cache_reg = CACHE_3;
        R_CACHE_3   : next_rd_cache_reg = CACHE_4;
        R_CACHE_4   : next_rd_cache_reg = CACHE_5;
        R_CACHE_5   : next_rd_cache_reg = CACHE_6;
        R_CACHE_6   : next_rd_cache_reg = CACHE_7;
        R_CACHE_7   : next_rd_cache_reg = CACHE_0;
        default     : next_rd_cache_reg = CACHE_0;
      endcase
    end

    always @(posedge clkr or negedge rstn_rclk)
    begin
        if(~rstn_rclk)
            cache_wr_state <= 'd0;
        else
            cache_wr_state <= cache_wr_next_state;
    end

    always @(*)
    begin
        case(cache_wr_state)
            W_CACHE_IDLE:
            begin
                if(start_send_e_i)
                    cache_wr_next_state = W_CACHE_IDLE;
                else if(fifo_data_vld)
                    cache_wr_next_state = W_CACHE_NEXT;
                else
                    cache_wr_next_state = W_CACHE_IDLE;
            end
            W_CACHE_01, W_CACHE_23, W_CACHE_45, W_CACHE_67:
            begin
                if(start_send_e_i)
                    cache_wr_next_state = W_CACHE_IDLE;
                else if(fifo_data_vld)
                    cache_wr_next_state = W_CACHE_NEXT;
                else
                    cache_wr_next_state = cache_wr_state;
            end
            default: cache_wr_next_state = W_CACHE_IDLE;
        endcase
    end

    wire [127 : 0]      fifo_dout;

    always @(posedge clkr or negedge rstn_rclk)
    begin
        if(~rstn_rclk)
        begin
            CACHE_0 <= 'd0;
            CACHE_1 <= 'd0;
            CACHE_2 <= 'd0;
            CACHE_3 <= 'd0;
            CACHE_4 <= 'd0;
            CACHE_5 <= 'd0;
            CACHE_6 <= 'd0;
            CACHE_7 <= 'd0;
        end
        else if(fifo_data_vld)
        begin
            case(cache_wr_state)
                W_CACHE_IDLE: {CACHE_1, CACHE_0} <= fifo_dout;
                W_CACHE_01:   {CACHE_3, CACHE_2} <= fifo_dout;
                W_CACHE_23:   {CACHE_5, CACHE_4} <= fifo_dout;
                W_CACHE_45:   {CACHE_7, CACHE_6} <= fifo_dout;
                W_CACHE_67:   {CACHE_1, CACHE_0} <= fifo_dout;
                default:      {CACHE_1, CACHE_0} <= fifo_dout;
            endcase
        end
    end

    assign fifo_data_vld = fifo_rd_en_ff2;

    always @(posedge clkr or negedge rstn_rclk)
    begin
        if(~rstn_rclk)
        begin
            fifo_rd_en_ff1 <= 1'b0;
            fifo_rd_en_ff2 <= 1'b0;
        end
        else
        begin
            fifo_rd_en_ff1 <= fifo_rd_en;
            fifo_rd_en_ff2 <= fifo_rd_en_ff1;
        end
    end

    always @(posedge clkr or negedge rstn_rclk)
    begin
        if(~rstn_rclk)
        begin
            fifo_rd_en    <= 1'b0;
            read_fifo_cnt <= 'd0;
        end
        else if(start_send_e_i)
        begin
            fifo_rd_en    <= 1'b0;
            read_fifo_cnt <= 'd0;
        end
        else if(write_active && (MEM_MAX_DEPTH >= 'd16 + (read_fifo_cnt - axis_write_data_cnt)) && (read_fifo_cnt < frame_len_r))
        begin
            fifo_rd_en    <= 1'b1;
            read_fifo_cnt <= read_fifo_cnt + 'd16;
        end
        else
        begin
            fifo_rd_en    <= 1'b0;
            read_fifo_cnt <= read_fifo_cnt;
        end
    end

    always @(posedge clkr or negedge rstn_rclk)
    begin
        if(~rstn_rclk)
            read_fifo_complete_cnt <= 'd0;
        else if(start_send_e_i)
            read_fifo_complete_cnt <= 'd0;
        else if(fifo_data_vld)
            read_fifo_complete_cnt <= read_fifo_complete_cnt + 'd16;
        else
            read_fifo_complete_cnt <= read_fifo_complete_cnt;
    end

    always @(posedge clkr or negedge rstn_rclk)
    begin
        if(~rstn_rclk)
            cache_rd_state <= 'd0;
        else
            cache_rd_state <= cache_rd_next_state;
    end

    always @(posedge clkr or negedge rstn_rclk)
    begin
        if(~rstn_rclk)
            write_index <= 'd0;
        else if(start_send_e_i)
            write_index <= 'd0;
        else if(wnext && (write_index != write_len))
            write_index <= write_index + 1'b1;
        else
            write_index <= write_index;
    end
    
    reg fifo_data_vld_ff;
    always @(posedge clkr or negedge rstn_rclk)
    begin
        if(~rstn_rclk)
            fifo_data_vld_ff <= 'd0;
        else
            fifo_data_vld_ff <= fifo_data_vld;
    end
    
    always @(*)
    begin
        case(cache_rd_state)
            R_CACHE_IDLE:
                if(start_send_e_i)
                    cache_rd_next_state = R_CACHE_IDLE;
                else if(write_active && (axis_write_data_cnt < read_fifo_complete_cnt)) // put new data
                    cache_rd_next_state = R_CACHE_NEXT;
                else
                    cache_rd_next_state = R_CACHE_IDLE;
            R_CACHE_0, R_CACHE_1, R_CACHE_2, R_CACHE_3,
            R_CACHE_4, R_CACHE_5, R_CACHE_6, R_CACHE_7:
                if(start_send_e_i)
                    cache_rd_next_state = R_CACHE_IDLE;
                else if(wnext) // last data has been accepted
                begin
                    if(((axis_write_data_cnt < read_fifo_complete_cnt)||fifo_data_vld_ff) && (write_index < write_len))
                        cache_rd_next_state = R_CACHE_NEXT;
                    else if(write_index == write_len) //end of a transaction
                        cache_rd_next_state = R_CACHE_IDLE;
                    else //new data has not come, wait
                        cache_rd_next_state = cache_rd_state;
                end
                else if((axis_write_data_cnt < read_fifo_complete_cnt) && last_data_accepted)
                    cache_rd_next_state = R_CACHE_NEXT;
                else //current data has not been accepted by slave, maintain set tvalid to wait
                    cache_rd_next_state = cache_rd_state;
            default:
                cache_rd_next_state = R_CACHE_IDLE;
        endcase
    end

    always @(posedge clkr or negedge rstn_rclk)
    begin
        if(~rstn_rclk)
        begin
            axi_ttvalid_o       <= 'd0;
            axi_ttlast_o        <= 'd0;
            axi_ttkeep_o        <= 'd0;
            axi_ttdata_o        <= 'd0;
            axis_write_data_cnt <= 'd0;
            last_data_accepted  <= 'd0;
        end
        else case (cache_rd_state)
            R_CACHE_IDLE:
                if(start_send_e_i)
                begin
                    axi_ttvalid_o       <= 'd0;
                    axi_ttlast_o        <= 'd0;
                    axi_ttkeep_o        <= 'd0;
                    axi_ttdata_o        <= 'd0;
                    axis_write_data_cnt <= 'd0;
                    last_data_accepted  <= 'd0;
                end
                else if(write_active && (axis_write_data_cnt < read_fifo_complete_cnt)) //put new data
                begin
                    axi_ttdata_o  <= next_rd_cache_reg;
                    axi_ttvalid_o <= 1'b1;
                    if(write_len == 'd0)
                    begin
                        axi_ttlast_o        <= 1'b1;
                        axi_ttkeep_o        <= tkeep_last;
                        axis_write_data_cnt <= axis_write_data_cnt + last_data_valid_byte;
                    end
                    else
                    begin
                        axi_ttlast_o         <= 1'b0;
                        axi_ttkeep_o         <= {8{1'b1}};
                        axis_write_data_cnt  <= axis_write_data_cnt + 'd8;
                    end
                end
                else
                begin
                    axi_ttvalid_o       <= 1'b0;
                    axis_write_data_cnt <= 'd0;
                end
            R_CACHE_0, R_CACHE_1, R_CACHE_2, R_CACHE_3,
            R_CACHE_4, R_CACHE_5, R_CACHE_6, R_CACHE_7:
                if(start_send_e_i)
                begin
                    axi_ttvalid_o       <= 'd0;
                    axi_ttlast_o        <= 'd0;
                    axi_ttkeep_o        <= 'd0;
                    axi_ttdata_o        <= 'd0;
                    axis_write_data_cnt <= 'd0;
                    last_data_accepted  <= 'd0;
                end
                else if(wnext || last_data_accepted) //last data has been accepted by slave
                begin
                    if(((axis_write_data_cnt < read_fifo_complete_cnt)||fifo_data_vld_ff) && (write_index < write_len))
                    begin
                        last_data_accepted <= 1'b0;
                        axi_ttdata_o       <= next_rd_cache_reg;
                        axi_ttvalid_o      <= 1'b1;
                        if((write_index == write_len - 'd1) && wnext)
                        begin
                            axi_ttlast_o   <= 1'b1;
                            axi_ttkeep_o   <= tkeep_last;
                            axis_write_data_cnt <= axis_write_data_cnt + last_data_valid_byte;
                        end
                        else
                        begin
                            axi_ttlast_o        <= 1'b0;
                            axi_ttkeep_o        <= {8{1'b1}};
                            axis_write_data_cnt <= axis_write_data_cnt + 'd8;
                        end
                    end
                    else if((write_index == write_len) && (axis_write_data_cnt < read_fifo_complete_cnt) && last_data_accepted)
                    begin
                        last_data_accepted <= 1'b0;
                        axi_ttdata_o     <= next_rd_cache_reg;
                        axi_ttvalid_o    <= 1'b1;
                        axi_ttlast_o   <= 1'b1;
                        axi_ttkeep_o   <= tkeep_last;
                        axis_write_data_cnt <= axis_write_data_cnt + last_data_valid_byte;
                    end
                    else if((write_index == write_len) && (frame_len_r-axis_write_data_cnt <= 'd8) && wnext) //last data
                    begin
                        axi_ttlast_o        <= 1'b0;
                        axi_ttvalid_o       <= 1'b0;
                        axis_write_data_cnt <= 'd0;
                    end
                    else //new data has not come, wait
                    begin
                        axi_ttvalid_o       <= 1'b0;
                        axis_write_data_cnt <= axis_write_data_cnt;
                        last_data_accepted  <= 1'b1;
                    end
                end
                else //last data has not been accepted by slave, set valid and wait
                begin
                    axi_ttvalid_o       <= 1'b1;
                    axis_write_data_cnt <= axis_write_data_cnt;
                end
            default:
                begin
                    axi_ttvalid_o       <= 'd0;
                    axi_ttlast_o        <= 'd0;
                    axi_ttkeep_o        <= 'd0;
                    axi_ttdata_o        <= 'd0;
                    axis_write_data_cnt <= 'd0;
                    last_data_accepted  <= 'd0;
                end
        endcase
    end

`ifdef VCS_MODEL
    native_fifo_2T #( .DW(DMA_DWIDTH), .AW(MEM_AWIDTH) ) u_axi_asfifo
    (
        .wclk          ( clkw             ),
        .wr_rstn       ( rstn_wclk        ),
        .rclk          ( clkr             ),
        .rd_rstn       ( rstn_rclk        ),

        .wr_en_i       ( wr_tx_fifo_e_i   ),
        .wr_din_i      ( wr_tx_fifo_data_i),
        .full_o        ( tx_fifo_full_o   ),
        .rd_en_i       ( fifo_rd_en       ),
        .rd_dout_o     ( fifo_dout        ),
        .empty_o       (                  )
    );
`else
    async_fifo_d2048_w128 u_axi_asfifo_tx_10g
    (
        .wclk          ( clkw             ),
        .wr_rstn       ( rstn_wclk        ),
        .rclk          ( clkr             ),
        .rd_rstn       ( rstn_rclk        ),

        .wr_en_i       ( wr_tx_fifo_e_i   ),
        .wr_din_i      ( wr_tx_fifo_data_i),
        .full_o        ( tx_fifo_full_o   ),
        .rd_en_i       ( fifo_rd_en       ),
        .rd_dout_o     ( fifo_dout        ),
        .empty_o       (                  ),
        .ram_dp_cfg_register    (ram_dp_cfg_register)
    );
`endif

endmodule
